
// ************************************
// * GStore - Inventory System - thomasfn *
// ************************************
// SHARED

ITEM.Classname = "base_item"
ITEM.Printname = "Base Item"

ITEM.Description = "All GStore items derive from this item!"
ITEM.Author = "thomasfn"

ITEM.Mass = 0.1 -- Kilograms
ITEM.Model = "chef_hat.mdl" -- Relative to "models/"

ITEM.Mins = Vector( -8, -8, 0 )
ITEM.Maxs = Vector( 8, 8, 16 )

ITEM.CanPickup = true
ITEM.CanDrop = true
ITEM.DropOnDeath = true
ITEM.CanUse = true
ITEM.CanEquip = false

ITEM.InWorld = false

ITEM.RootBase = ITEM


function ITEM:Init( id )
	// Item is being created
	// This is called SHARED
	self._id = id
	
	if (CLIENT) then
		local mdl = ClientsideModel( "models/" .. self.Model )
		self.Entity = mdl
	end
	
	if (SERVER) then self.Revision = 1 end
end

local trace = {}
local vecUp = Vector( 0, 0, 1 )
local vecDown = Vector( 0, 0, -1 )
function ITEM:Tick()
	// Item ticks
	trace.start = self:GetPos()
	trace.endpos = trace.start + (vecDown * 256)
	trace.filter = player.GetAll()
	//trace.mask = MASK_SOLID_BRUSHONLY
	local tr = util.TraceLine( trace )
	if (tr.Hit) then
		self:SetPos( tr.HitPos )
	else
		self:SetPos( tr.HitPos + (vecUp*(self:OBBCenter().z)) )
	end
	if (CLIENT) then
		if (self:GetPos():Distance( LocalPlayer():GetShootPos() ) > GSTORE_ITEMRANGE) then
			print( "[GSTORE] Item removed! (Out of range) [" .. tostring( self._id ) .. "] (" .. self.Classname .. ")" )
			self:Destroy()
			return
		end
		local ent = self.Entity
		if ValidEntity( ent ) then
			ent:SetPos( self:GetPos() )
			ent:SetNoDraw( not self.InWorld )
		end
	end
end

function ITEM:Index()
	return self._id
end

function ITEM:SetPos( pos )
	self.Position = pos
	if (CLIENT && ValidEntity( self.Entity )) then self.Entity:SetPos( pos ) end
end

function ITEM:GetPos()
	if (self.InWorld) then return self.Position end
	if (self.Container) then return self.Container:GetPos() end
	return Vector( 0, 0, 0 )
end

function ITEM:GetMass()
	return self.Mass
end

function ITEM:OBBMins()
	return self.Mins
end

function ITEM:OBBMaxs()
	return self.Maxs
end

function ITEM:OBBCenter()
	return (self:OBBMins() + self:OBBMaxs())*0.5
end

function ITEM:IsValid()
	return not self.Invalid -- lol
end

if (SERVER) then

	function ITEM:OnUse( ply )
		// Item is in a player's inventory
		// self.Container = container holding the item
		// This is called SERVERSIDE
		Msg( "[GSTORE] " .. tostring( ply ) .. " used a '" .. self.Classname .. "'!" )
		//ply:ChatPrint( "You used the " .. self.Printname .. "!" )
	end

	function ITEM:OnDrop( ply )
		// Item was in a player's inventory
		// self.Owner = player who dropped the item
		// This is called SERVERSIDE
		Msg( "[GSTORE] " .. tostring( ply ) .. " dropped a '" .. self.Classname .. "'!" )
		//ply:ChatPrint( "You dropped the " .. self.Printname .. "!" )
	end

	function ITEM:OnPickup( ply )
		// Item is now in a player's inventory
		// self.Owner = player holding the item
		// self.Container = container holding the item
		// This is called SERVERSIDE
		Msg( "[GSTORE] " .. tostring( ply ) .. " picked up a '" .. self.Classname .. "'!" )
		//ply:ChatPrint( "You picked up the " .. self.Printname .. "!" )
	end
	
	function ITEM:OnEquip( ply, b )
		// Item is in a player's inventory
		// Item has been EQUIPPED
		// This is called SERVERSIDE
		if (b) then
			Msg( "[GSTORE] " .. tostring( ply ) .. " equipped a '" .. self.Classname .. "'!" )
			//ply:ChatPrint( "You equipped the " .. self.Printname .. "!" )
		else
			Msg( "[GSTORE] " .. tostring( ply ) .. " unequipped a '" .. self.Classname .. "'!" )
			//ply:ChatPrint( "You unequipped the " .. self.Printname .. "!" )
		end
	end
	
	function ITEM:CanCombineWith( ply, with )
		// Item is in a player's inventory
		// Return true to allow a combine to happen
		// This is called SERVERSIDE
		return false
	end
	
	function ITEM:DoCombineWith( ply, item )
		// Item is in a player's inventory
		// Return true to allow a combine to happen
		// This is called SERVERSIDE
		Msg( "[GSTORE] " .. tostring( ply ) .. " combined a '" .. self.Classname .. "' with a '" .. item.Classname .. "'!\n" )
		//ply:ChatPrint( "The " .. self.Printname .. " was combined with the " .. item.Printname .. "!" )
	end

	----------------------------

	function ITEM:Use()
		self:OnUse( self.Container.Owner )
	end
	
	function ITEM:Equip( ply, b )
		if (b) then
			if (ply.EquipItem && ply.EquipItem:IsValid()) then
				ply.EquipItem:Equip( ply, false )
			end
			ply.EquipItem = self
		end
		self.Equipped = b
		self:Sync( ply )
		self:OnEquip( ply, b )
	end

	function ITEM:Destroy()
		if (self.Container) then self.Container:RemoveItem( self ) end
		if (self.OnRemove) then self:OnRemove() end
		gstore.KillItem( self )
	end
	
	function ITEM:Combine( ply, with )
		if (with:CanCombineWith( ply, self )) then
			with:DoCombineWith( ply, self )
			return true
		end
		if (self:CanCombineWith( ply, with )) then
			self:DoCombineWith( ply, with )
			return true
		end
		return false
	end

	function ITEM:Sync( target )
		umsg.Start( "gs_item", target )
			umsg.Short( self._id )
			umsg.String( self.Classname )
			umsg.Bool( self.InWorld )
			umsg.Vector( self:GetPos() )
			umsg.Short( (self.Container and self.Container._id) or 0 )
			umsg.Bool( self.Equipped )
			if (self.Manifest) then self:Manifest() end
		umsg.End()
		if (!target) then return end
		if (!target.GStore) then target.GStore = {} end
		target.GStore[ self ] = self.Revision
	end
	
	function ITEM:UpdateNeeded()
		self.Revision = self.Revision + 1
	end
	
	function ITEM:InRange( of )
		return self:GetPos():Distance( of:GetPos() ) <= GSTORE_ITEMRANGE
	end
	
	function ITEM:ToWorld( pos )
		self.InWorld = true
		self:SetPos( pos )
		if (self.Container) then self.Container:RemoveItem( self ) end
		self:UpdateNeeded()
	end
	
	function ITEM:FromWorld( c )
		self.InWorld = false
		self.Container = c
		self:UpdateNeeded()
		if (c && c.Owner) then self:OnPickup( c.Owner ) end
	end
	
	function ITEM:RandomPos( origin, offset )
		return origin + Vector( math.random( -(offset*0.5), (offset*0.5) ), math.random( -(offset*0.5), (offset*0.5) ), 0 )
	end
	
	function ITEM:ShouldSync( ply )
		if (!ply.GStore) then ply.GStore = {} end
		local plRev = ply.GStore[ self ] or 0 -- This is the revision the player currently has
		local dist = ply:GetPos():Distance( self:GetPos() )
		if (self.NoDistanceRequirement) then dist = GSTORE_ITEMRANGE end
		if (dist > GSTORE_ITEMRANGE) then
			plRev = 0
			ply.GStore[ self ] = nil
		else
			return (self.Revision > plRev)
		end
	end

end

----------------------------

if (CLIENT) then

	function ITEM:Get2DBox()
		local w = Vector( self:OBBMaxs().x-self:OBBMins().x, 0, 0 )
		local l = Vector( 0, self:OBBMaxs().y-self:OBBMins().y, 0 )
		local h = Vector( 0, 0, self:OBBMaxs().z-self:OBBMins().z )
		
		local a = self:OBBMins()+self:GetPos()
		local b = a + w
		local c = b + h
		local d = a + h
		
		local e = self:OBBMaxs()+self:GetPos()
		local f = e - w
		local g = f - h
		local h = e - h
		
		local as, bs, cs, ds, es, fs, gs, hs = a:ToScreen(), b:ToScreen(), c:ToScreen(), d:ToScreen(), e:ToScreen(), f:ToScreen(), g:ToScreen(), h:ToScreen()
		local upperBound = math.min( as.y, bs.y, cs.y, ds.y, es.y, fs.y, gs.y, hs.y )
		local lowerBound = math.max( as.y, bs.y, cs.y, ds.y, es.y, fs.y, gs.y, hs.y )
		local leftBound = math.min( as.x, bs.x, cs.x, ds.x, es.x, fs.x, gs.x, hs.x )
		local rightBound = math.max( as.x, bs.x, cs.x, ds.x, es.x, fs.x, gs.x, hs.x )
		
		return leftBound, upperBound, (rightBound-leftBound), (lowerBound-upperBound)
	end

	function ITEM:Update( um )
	
	end
	
	function ITEM:Destroy()
		gstore.KillItem( self )
		local ent = self.Entity
		if ValidEntity( ent ) then
			ent:Remove()
		end
	end
	
	function ITEM:InPickupRange()
		//local tr = LocalPlayer():GetEyeTrace()
		//local pos = tr.HitPos-self:GetPos()
		//return (tr.HitPos:Distance( LocalPlayer():GetShootPos() ) <= GSTORE_USERANGE) &&
		/*return	(pos.x >= self:OBBMins().x) && (pos.x <= self:OBBMaxs().x) && (pos.y >= self:OBBMins().y) &&
				(pos.y <= self:OBBMaxs().y) && (pos.z >= (self:OBBMins().z-2)) && (pos.z <= (self:OBBMaxs().z+2));*/
		
		local pos = self:GetPos() * 1 -- Make a copy
		local shootpos = LocalPlayer():GetShootPos()
		pos.z = 0
		shootpos.z = 0
		local dist = pos:Distance( shootpos )
		if (dist > GSTORE_USERANGE) then return false end
		local zdist = self:GetPos().z - LocalPlayer():GetShootPos().z
		if (zdist < 0) then zdist = -zdist end
		if (zdist > (2*GSTORE_USERANGE)) then return false end
		//if (self:GetPos():Distance( LocalPlayer():GetShootPos() ) > GSTORE_USERANGE) then return false end
		local aimvec = LocalPlayer():GetAimVector()
		local diff = self:GetPos() - LocalPlayer():GetShootPos()
		diff.z = 0
		aimvec.z = 0
		aimvec = aimvec:Normalize()
		diff = diff:Normalize()
		return aimvec:Dot( diff ) >= 0.85
	end
	
	local function IsBehind( posA, posB, normal )
		local Vec1 = ( posB - posA ):GetNormalized()
		return ( normal:Dot( Vec1 ) < 0 )
	end
	
	local font = "ScoreboardText"
	local function Text( str, x, y, col )
		draw.SimpleText( str, font, x+1, y+1, color_black, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP )
		draw.SimpleText( str, font, x, y, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP )
	end
	
	local DrawRat = 0.15
	local mDrawRat = 1-DrawRat
	
	local colOuter = Color( 0, 0, 255 )
	local colInner = Color( 0, 0, 200 )
	
	function ITEM:PaintHUD()
		// if (IsBehind( self:GetPos(), EyePos(), EyeAngles():Forward() )) then return end
		if (!self.InWorld) then return end
		local dist = self:GetPos():Distance( LocalPlayer():GetShootPos() )
		if (dist > GSTORE_HUD_DISTANCE) then return end
		local x, y, w, h = self:Get2DBox()
		
		/*// local t = math.ceil( (GSTORE_HUD_DISTANCE*GSTORE_HUD_THICKNESS) / dist )
		local t = GSTORE_HUD_THICKNESS
		
		for o=0, t-1 do
		
			surface.SetDrawColor( ((o == (t-1)) and colInner) or colOuter )
		
			// Top left
			surface.DrawLine( x+o, y+o, x+(w*DrawRat)-o, y+o )
			surface.DrawLine( x+o, y+o, x+o, y+(h*DrawRat)-o )
			
			// Top right
			surface.DrawLine( x+(w*mDrawRat)+o, y+o, x+w-o, y+o )
			surface.DrawLine( x+w-o, y+o, x+w-o, y+(h*DrawRat)-o )
			
			// Bottom left
			surface.DrawLine( x+o, y+h-o, x+(w*DrawRat)-o, y+h-o )
			surface.DrawLine( x+o, y+(h*mDrawRat)+o, x+o, y+h-o )
			
			// Bottom right
			surface.DrawLine( x+(w*mDrawRat)+o, y+h-o, x+w-o, y+h-o )
			surface.DrawLine( x+w-o, y+(h*mDrawRat)+o, x+w-o, y+h-o )
			
		end*/
		
		if (self:InPickupRange()) then
			
			Text( self.Printname, x+(w*0.5), y-30, color_white )
			Text( tostring( self:GetMass() ) .. " KG", x+(w*0.5), y-10, color_white )
			
		end

	end

end